/*
 * Copyright 2013 Rivaldo.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jety.commons.domain;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.jety.commons.typechecker.AbstractTypeChecker;

/**
 *
 * @author Rivaldo
 */
public class HandleEntry<T> {

    private final T handler;
    private Map<String, String[]> signatures;
    private Set<Method> methods;

    public HandleEntry(AbstractTypeChecker typeChecker, T handler, Class<T>... classes) throws IllegalArgumentException {
        checkArgs(handler, classes);
        this.handler = handler;
        fillData(typeChecker, classes);
    }

    public T getHandler() {
        return handler;
    }

    public Map<String, String[]> getSignatures() {
        return signatures;
    }

    public Set<Method> getMethods() {
        return methods;
    }

    private void fillData(AbstractTypeChecker typeChecker, Class<T>[] classes) {

        methods = new HashSet<Method>();

        for (Class<?> clazz : classes) {
            typeChecker.checkInterface(clazz);
            methods.addAll(getMethods(clazz));
        }

        Map<String, List<String>> map = getParameters(methods, typeChecker);
        fillSignatures(map);
        lockData();
    }

    private void checkArgs(T handler, Class<T>[] classes) throws IllegalArgumentException {

        if (handler == null) {
            throw new IllegalArgumentException("Handler is null");
        }

        if (classes.length == 0) {
            throw new IllegalArgumentException("At least one interface has to be mentioned");
        }
    }

    private String getParametersName(AbstractTypeChecker typeChecker, Class<?> returnType, Class<?>[] params) {

        StringBuilder buff = new StringBuilder(typeChecker.getTypeName(returnType));
        for (int i = 0; i < params.length; i++) {
            buff.append(",").append(typeChecker.getTypeName(params[i]));
        }
        return buff.toString();
    }

    private Set<Method> getMethods(Class<?> clazz) {

        Set<Method> methodSet = new HashSet<Method>();

        for (Method method : clazz.getMethods()) {
            methodSet.add(method);
        }

        return methodSet;

    }

    private Map<String, List<String>> getParameters(Set<Method> methodSet, AbstractTypeChecker typeChecker) {

        Class<?>[] params;
        List<String> list;

        Map<String, List<String>> map = new HashMap<String, List<String>>();

        for (Method method : methodSet) {
            params = method.getParameterTypes();

            list = map.get(method.getName());
            if (list == null) {
                list = new ArrayList<String>();
            }

            String paramsStr = getParametersName(typeChecker, method.getReturnType(), params);
            list.add(paramsStr);
            map.put(method.getName(), list);
        }

        return map;
    }

    private void fillSignatures(Map<String, List<String>> map) {
        signatures = new TreeMap<String, String[]>();
        for (Map.Entry<String, List<String>> e : map.entrySet()) {
            String[] arr = new String[e.getValue().size()];
            signatures.put(e.getKey(), e.getValue().toArray(arr));
        }
    }

    private void lockData() {
        this.methods = Collections.unmodifiableSet(this.methods);
        this.signatures = Collections.unmodifiableMap(this.signatures);
    }
}